#ifndef MODEL_CC
#define MODEL_CC
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<iterator>
#include<vector>
#include<map>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include "def.hpp"
#include "metamodel.hpp"
#include "model.hpp"
#include "model_fm.hpp"
#include "log.hpp"
#include "input_feature.hpp"

using namespace std;

namespace Puza{
	PuzaMatrixModel* PuzaMatrixModel::m_pInstance = NULL;

	PuzaMatrixModel* PuzaMatrixModel::Instance(){
		if (!m_pInstance)   // Only allow one instance of class to be generated.
			m_pInstance = new PuzaMatrixModel;
		return m_pInstance;
	}

	PuzaMatrixModel::PuzaMatrixModel(){ }

	void PuzaMatrixModel::PerformTask(){
		PuzaLogger::Instance()->PutString("Start perform task.");
		if(PuzaDEF::Instance()->CURRENT_TASK_TYPE == TASK_TRAIN){
			// TASK_TRAIN is a special case for TASK_TRAIN_VALIDATION
			PuzaDEF::Instance()->TRAIN_THRESHOLD = 1.0;
			PuzaLogger::Instance()->PutString("TASK: TRAIN BY Stochastic Gradient Descent.");
			this->mf->TrainValidation();
		}
		if(PuzaDEF::Instance()->CURRENT_TASK_TYPE == TASK_TRAIN_VALIDATION){
			PuzaLogger::Instance()->PutString("TASK: TRAIN and Validation BY Stochastic Gradient Descent.");
			this->mf->TrainValidation();
		}
		PuzaLogger::Instance()->PutString("Completed perform task.");
	}

	void PuzaMatrixModel::Run(){
	    // check the current model choice
		PuzaLogger::Instance()->PutString("Start Run.");

		if(PuzaDEF::Instance()->TRAIN_TYPE == PROCESS_POINT){
			PuzaInputFeature::Instance()->Read_Simple();
		}
		else if(PuzaDEF::Instance()->TRAIN_TYPE == PROCESS_RANK){
			PuzaInputFeature::Instance()->Read_Session();
		}
		else{
			cout << "Train process error. We only support point-wise learning and rank-based learning." << endl;
			exit(0);
		}

		// init fm
	    if(PuzaDEF::Instance()->CURRENT_MODEL_TYPE == MODEL_FM){
	    	PuzaLogger::Instance()->PutString("Init Factorization Machines.");
	    	this->mf = new PuzaModelFM();
	    }

	    if(PuzaDEF::Instance()->TRAIN_TYPE == PROCESS_POINT){
	    	PuzaLogger::Instance()->PutString("Point Loss Function:" + to_string(PuzaDEF::Instance()->CURRENT_POINT_LOSS));
	    	this->PerformTask();
	    }
	    else{
	    	PuzaLogger::Instance()->PutString("Rank Loss Function:" + to_string(PuzaDEF::Instance()->CURRENT_RANK_LOSS));
	    	this->PerformTask();
	    }
	    PuzaLogger::Instance()->PutString("Completed Run.");
	    delete this->mf;
	}


}
#endif
